﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using Medianamik.Core.Configuration;
using Medianamik.Core.Sugar;
using Medianamik.Core.Urls;

namespace Medianamik.Core.CodeGeneration
{
    public class CommandBuilder
    {
        private static readonly DefaultColumns DefaultColumns = new DefaultColumns();
        private const string NotifyTableChangedStatement = "EXECUTE [dbo].[AspNet_SqlCacheUpdateChangeIdStoredProcedure]   @tableName";
        private static readonly MedianamikConfigSection Config =
            (MedianamikConfigSection)ConfigurationManager.GetSection(ConfigurationSectionName.MEDIANAMIK);

        private SqlGenerator _sql;
        protected SqlGenerator Sql
        {
            get
            {
                return _sql
                    ?? (_sql = new SqlGenerator());
            }
        }

        private SqlConnection _connection;
        public SqlConnection Connection
        {
            get { return _connection; }
            private set { _connection = value; }
        }

        private IUrlDictionary _urls;
        protected virtual IUrlDictionary Urls
        {
            get
            {
                return _urls
                    ?? (_urls = new ObjectFactory<IUrlDictionary>().Get());
            }

        }

        public CommandBuilder(SqlConnection pConnection)
        {
            Connection = pConnection;
        }

        //TODOMAX:
        public IDbCommand BuildInsertCommand(NodeInstance nodeInstance, bool publication)
        {
            var insertStatement = Sql.BuildInsertStatement(nodeInstance.Node.NodeType);

            IDbCommand command = Connection.CreateCommand();
            command.CommandText = "-- NodeID: " + nodeInstance.Node.ID
                                + " - Culture: " + nodeInstance.Culture.Name + "\n"
                                + insertStatement;
            var queryParams = PrepareParametersForInsertOrUpdate(nodeInstance, publication);
            queryParams.ForEach(p => command.Parameters.Add(p));

            return command;
        }

        //TODOMAX:
        public IDbCommand BuildUpdateCommand(NodeInstance nodeInstance)
        {
            var updateStatement = Sql.BuildUpdateStatement(nodeInstance);

            IDbCommand command = Connection.CreateCommand();
            command.CommandText = "-- NodeID: " + nodeInstance.Node.ID
                                + " - Culture: " + nodeInstance.Culture.Name + "\n"
                                + updateStatement;
            var queryParams = PrepareParametersForInsertOrUpdate(nodeInstance, false);
            queryParams.ForEach(p => command.Parameters.Add(p));

            return command;
        }

        public IDbCommand BuildDropViewCommand(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDropViewStatement(pNodeType);
            return command;
        }

        public IDbCommand BuildDropViewCommand(NodeTypeInterface pInterface)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDropViewStatement(pInterface);
            return command;
        }

        public IDbCommand BuildCreateViewCommand(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildCreateViewStatement(pNodeType);
            return command;
        }

        public IDbCommand BuildCreateViewCommand(NodeTypeInterface pInterface,
            IEnumerable<NodeType> implementers)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildCreateViewStatement(pInterface, implementers);
            return command;
        }

        public IDbCommand BuildDropTableCommand(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDropTableStatement(pNodeType);
            return command;
        }

        public IDbCommand BuildCreateTableCommand(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildCreateTableStatement(pNodeType);
            return command;
        }

        //public IDbCommand BuildCreateGenerationTriggerCommand(NodeType pNodeType)
        //{
        //    var command = Connection.CreateCommand();
        //    command.CommandText = Sql.BuildCreateGenerationTriggerStatement(pNodeType);
        //    return command;
        //}

        public IEnumerable<IDbCommand> BuildAddColumnsCommand(IEnumerable<NodeTypeProperty> pNodeTypeProperties, NodeType pNodeType)
        {
            var commands = new HashSet<IDbCommand>();

            foreach (var property in pNodeTypeProperties)
            {
                var command = Connection.CreateCommand();
                command.CommandText = Sql.BuildAddColumnStatement(property, pNodeType);
                commands.Add(command);
            }

            return commands;
        }

        public IEnumerable<IDbCommand> BuildRemoveColumnsCommand(IEnumerable<NodeTypeProperty> pNodeTypeProperties, NodeType pNodeType)
        {
            var commands = new HashSet<IDbCommand>();

            foreach (var property in pNodeTypeProperties)
            {
                var command = Connection.CreateCommand();
                command.CommandText = Sql.BuildRemoveColumnStatement(property, pNodeType);
                commands.Add(command);
            }

            return commands;
        }

        public IDbCommand BuildTableExistsCommand(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildTableExistsStatement(pNodeType);

            return command;
        }

        public IDbCommand BuildDeleteDraftAndPublishedCommand(NodeInstance nodeInstance)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDeleteDraftAndPublishedStatement(nodeInstance.Node.NodeType);
            var queryParams = PrepareParametersForDeleteAll(nodeInstance);
            queryParams.ForEach(p => command.Parameters.Add(p));

            return command;
        }

        public IDbCommand BuildDeleteDraftRecordCommand(NodeInstance nodeInstance)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDeleteDraftOrPublishedStatement(nodeInstance.Node.NodeType);
            var queryParams = PrepareParametersForDelete(nodeInstance, -1);
            queryParams.ForEach(p => command.Parameters.Add(p));

            return command;
        }
        public IDbCommand BuildDeletePublishedRecordCommand(NodeInstance nodeInstance)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDeleteDraftOrPublishedStatement(nodeInstance.Node.NodeType);
            var queryParams = PrepareParametersForDelete(nodeInstance, 0);
            queryParams.ForEach(p => command.Parameters.Add(p));

            return command;
        }

        public IDbCommand BuildNotifyTableChangedCommand(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = NotifyTableChangedStatement;
            command.Parameters.Add(new SqlParameter("@tableName", new SqlNameGenerator(pNodeType).TableNameSimple));
            return command;
        }

        public IDbCommand BuildDisableTableConstraints(string tableName)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDisableTableConstraintsStatement(tableName);
            return command;
        }

        public IDbCommand BuildEnableTableConstraints(string tableName)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildEnableTableConstraintsStatement(tableName);
            return command;
        }

        public IDbCommand BuildEnableTableConstraint(string tableName, string constraintName)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildEnableTableConstraintStatement(tableName, constraintName);
            return command;
        }

        public IDbCommand BuildDisableTableConstraint(string tableName, string constraintName)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDisableTableConstraintStatement(tableName, constraintName);
            return command;
        }

        public IDbCommand BuildDisableTableTriggers(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDisableTableTriggersStatement(pNodeType);
            return command;
        }

        public IDbCommand BuildEnableTableTriggers(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildEnableTableTriggersStatement(pNodeType);
            return command;
        }

        public IDbCommand BuildSwitchToNonVersionableTypeMaintenanceCommand(NodeType pNodeType)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildSwitchToNonVersionableTypeMaintenanceStatement(pNodeType);
            command.Parameters.Add(new SqlParameter("@TypeId", pNodeType.ID.ToString()));
            return command;

        }

        public IDbCommand BuildDestroyDeletedNodesCommand()
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildDestroyDeletedNodesStatement();
            return command;
        }

        public IDbCommand BuildCleanUpGenTablesCommand()
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildCleanUpGenTablesStatement();
            return command;
        }

        public IDbCommand BuildCleanUpRevisionsCommand(bool top)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql.BuildCleanUpRevisionsStatement(top);

            return command;
        }

        public IDbCommand BuildRemoveNeutralLangInstancesForNonNeutralLangNodeCommand(IEnumerable<NodeType> nodeTypes)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql
                .BuildRemoveNeutralLangInstancesForNonNeutralLangNodeStatement(nodeTypes);

            return command;
        }

        public IDbCommand BuildRemoveNonNeutralLangInstancesForNeutralLangNodeCommand(IEnumerable<NodeType> nodeTypes)
        {
            var command = Connection.CreateCommand();
            command.CommandText = Sql
                .BuildRemoveNonNeutralLangInstancesForNeutralLangNodeStatement(nodeTypes);

            return command;
        }

        //TODOMAX:
        private SqlParameter[] PrepareParametersForInsertOrUpdate(NodeInstance nodeInstance, 
            bool publication)
        {
            var paramList = new List<SqlParameter>();
            var properties = nodeInstance.GetPropertiesForRevision(publication ? 
                DateTime.MaxValue : default(DateTime?))
                .Where(p => p.IsOwnProperty);

            paramList.Add(new SqlParameter(DefaultColumns.Revision.ParameterName,
                 (object)DBNull.Value));
            paramList.Add(new SqlParameter(DefaultColumns.NodeId.ParameterName,
                nodeInstance.Node.ID));
            paramList.Add(new SqlParameter(DefaultColumns.CultureName.ParameterName,
                nodeInstance.Culture.Name));
            paramList.Add(new SqlParameter(DefaultColumns.IsActive.ParameterName,
                nodeInstance.IsActive));
            paramList.Add(new SqlParameter(DefaultColumns.HasModifications.ParameterName,
                nodeInstance.Node.IsVersioned && nodeInstance.HasModifiedProperties));
            paramList.Add(new SqlParameter(DefaultColumns.IsSubEntity.ParameterName,
                nodeInstance.Node.IsSubEntity));
            paramList.Add(new SqlParameter(DefaultColumns.Generation.ParameterName,
                publication ? 0 : -1)); //int.MaxValue)); //Sera mis à jour par un trigger

            //Nouvelles colonnes pour éviter le union sur la table medianamik_node
            paramList.Add(new SqlParameter(DefaultColumns.CreatedOn.ParameterName,
                nodeInstance.Node.CreatedOn) { DbType = DbType.DateTime });

            paramList.Add(new SqlParameter(DefaultColumns.ExtendedData.ParameterName,
                nodeInstance.ExtendedData)
            {
                DbType = DbType.Xml
            });

            paramList.Add(new SqlParameter(DefaultColumns.Depth.ParameterName,
                nodeInstance.Node.Depth) { DbType = DbType.Int32 });
            paramList.Add(new SqlParameter(DefaultColumns.ParentNodeId.ParameterName,
                (object)nodeInstance.Node.ParentNodeId ?? DBNull.Value) { DbType = DbType.Guid });
            paramList.Add(new SqlParameter(DefaultColumns.Path.ParameterName,
                nodeInstance.Node.Path) { DbType = DbType.String });
            paramList.Add(new SqlParameter(DefaultColumns.CreatedBy.ParameterName,
               nodeInstance.Node.CreatedBy) { DbType = DbType.String });
            //paramList.Add(new SqlParameter(DefaultColumns.ModifiedBy.ParameterName,
            //   nodeInstance.Node.EditedBy) { DbType = DbType.String });
            paramList.Add(new SqlParameter(DefaultColumns.Position.ParameterName,
                nodeInstance.Node.Position) { DbType = DbType.Int16 });
            paramList.Add(new SqlParameter(DefaultColumns.TypeId.ParameterName,
                nodeInstance.Node.TypeId) { DbType = DbType.Guid });
            paramList.Add(new SqlParameter(DefaultColumns.ModifiedOn.ParameterName,
                (object)nodeInstance.Node.ModifiedOn ?? DBNull.Value) { DbType = DbType.DateTime }); //TODOMAX: Déplacer ModifiedOn sur NodeInstance

            properties.Where(p => p.NodeTypeProperty.IsGenerated).ForEach(p =>
                paramList.Add(new SqlParameter(new TableColumn(p.NodeTypeProperty).ParameterName,
                    PrepareNodePropertyValue(p))));

            return paramList.ToArray();
        }

        //TODOMAX:
        private SqlParameter[] PrepareParametersForUpdateColumnByGeneration(NodeProperty pNodeProperty,
            int pGeneration)
        {
            var paramList = new List<SqlParameter>
                {
                    new SqlParameter(DefaultColumns.NodeId.ParameterName, pNodeProperty.Node.ID),
                    new SqlParameter(DefaultColumns.Generation.ParameterName, pGeneration)
                };

            if (!pNodeProperty.IsLanguageNeutral)
            {
                paramList.Add(new SqlParameter(DefaultColumns.CultureName.ParameterName,
                    pNodeProperty.Culture.Name));
            }

            var column = new TableColumn(pNodeProperty.NodeTypeProperty);
            paramList.Add(new SqlParameter(column.ParameterName,
                PrepareNodePropertyValue(pNodeProperty)));

            return paramList.ToArray();
        }

        private static SqlParameter[] PrepareParametersForDelete(NodeInstance nodeInstance,
            int pGeneration)
        {
            var paramList = new List<SqlParameter>
                {
                    new SqlParameter(DefaultColumns.NodeId.ParameterName, nodeInstance.Node.ID),
                    new SqlParameter(DefaultColumns.CultureName.ParameterName,
                                     nodeInstance.Culture.Name),
                    new SqlParameter(DefaultColumns.Generation.ParameterName, pGeneration)
                };

            return paramList.ToArray();
        }

        private static SqlParameter[] PrepareParametersForDeleteAll(NodeInstance nodeInstance)
        {
            var paramList = new List<SqlParameter>
                {
                    new SqlParameter(DefaultColumns.NodeId.ParameterName, nodeInstance.Node.ID),
                    new SqlParameter(DefaultColumns.CultureName.ParameterName,
                                     nodeInstance.Culture.Name)
                };

            return paramList.ToArray();
        }

        private object PrepareNodePropertyValue(NodeProperty pProperty)
        {
            if (pProperty.Value == null)
            {
                return DBNull.Value;
            }

            //if (pProperty.Value is string) //toujours vrai...
            if (DataTypes.StringDataTypes.Contains(pProperty.DataType.ID))
            {
                //Le contenu de la propriété est un string, il faut chercher
                // d'éventuels NodeIds et les remplacer par leur Url
                var culture = pProperty.IsLanguageNeutral
                    ? CultureInfo.GetCultureInfo(Config.DefaultEditionCultureName)
                    : pProperty.Culture;

                var linkResolver = new LinkResolver(culture, Urls);

                return UrlAmpersandEncoder.Encode(linkResolver.Resolve((string)pProperty.Value));

            }
           
            return pProperty.Value.ToString().Length == 0 ? DBNull.Value : pProperty.Value;
        }
    }
}
